// License Notification
//======================================================
// Copyright (c) 2010 Channel Intelligence Inc.
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// which accompanies this distribution, and is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// ======================================================




using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Text;
using System.Reflection;
using CIP4.Common;
using CIP4.Item;
using CIP4.Simple;

namespace CIP4.DataProvider.Migrator
{
    /// <summary>
    /// Migrator Provider does the following
    /// 1. Its connection attribute provides connections for two providers, call them Target and Source
    /// 2. On GetItems, access item from source and target return merged result
    /// 3. On delete - delete from both target and source
    /// 4. On save - delete from source then save to target
    /// 
    /// Operationally move from one DataProvider to another is accomplished as follows
    /// 1. switch config to point to a migrator provider
    /// 2. recycle app (or switch providers using provider switcher)
    /// 3. Move the data by getting and saving every item. Any retrieves running concurrently 
    ///    with the move will behave correctly getting the data from source or target as appropriate
    /// 
    /// </summary>
    public class DataProvider : CIP4.Simple.DataProvider, IDataProvider
    {
        IDataProvider Source { get; set; }

        IDataProvider Target { get; set; }

        public StrongWrapperBase WrapperBase { get; set; }

        private ProviderTypesEnum SourceType;

        #region IDataProvider Members

        public DataProvider (CIP4ConnectionAttribute DataProviderConnection, IDataProvider cachingProvider, LoggerDelegate logger)
            : base (DataProviderConnection)
        {
            if (cachingProvider != null)
                LoggerWrapper(MajorEnum.Warning, MinorEnum.InitializingProvider, 
                    "Migrator DataProvider provider does not support caching", new object[] { }, logger);
            if (!(DataProviderConnection is CIP4MigratorConnectionAttribute))
                LoggerWrapper(MajorEnum.Error, MinorEnum.UnexpectedConnectionType,
                    "{0} is not an acceptable connection type", new object[] { DataProviderConnection.ToString() }, logger);
            else
            {
                LoggerWrapper(MajorEnum.Trace, MinorEnum.InitializingProvider,
                    "Initalizing Provider: {0}", new object[] { DataProviderConnection }, logger);
                CIP4MigratorConnectionAttribute connection = (CIP4MigratorConnectionAttribute)DataProviderConnection;
                if (connection.Source == null)
                    LoggerWrapper(MajorEnum.Error, MinorEnum.InitializingProvider,
                        "A Migrator connection attribute must have a Source", new object[] {  }, logger);
                else if (connection.Target == null)
                    LoggerWrapper(MajorEnum.Error, MinorEnum.InitializingProvider,
                        "A Migrator connection attribute must have a Target", new object[] { }, logger);
                else
                {
                    SourceType = connection.Source.ProviderKind;
                    Source = GetProvider(connection.Source, null, logger);
                    Target = GetProvider(connection.Target, null, logger);
                }
            }
        }

        public void DeleteCollection(CollectionType collection, LoggerDelegate logger)
        {
            LoggerDelegate localLogger = delegate(MajorEnum status, MinorEnum minorStatus, string message, object[] paras)
            {
                if (status != MajorEnum.Information)
                    logger(status, minorStatus, message, paras);
                return status == MajorEnum.Error ? LoggerResponseEnum.giveUp : LoggerResponseEnum.keepGoing;
            };
            Target.DeleteCollection(collection, logger);
            Source.DeleteCollection(collection, localLogger);
        }

        public override IEnumerable<object> GetItems(CollectionType collection, IBooleanValue selectionExpression, CursorType cursor)
        {
            List<object> items = new List<object>();

            // The code assumes that an item will never exist in both source and target
            items.AddRange(Source.GetItems(collection, selectionExpression, cursor));
            items.AddRange(Target.GetItems(collection, selectionExpression, cursor));

            return items;
        }

        public override void PurgeItems(IEnumerable<ExternalIDType> externalIDs, CollectionType collection, GetSerializerDelegate getSerializer)
        {
            Source.PurgeItems(externalIDs, collection, getSerializer);
            Target.PurgeItems(externalIDs, collection, getSerializer);
        }

        public void SaveItems(IEnumerable<object> items, CollectionType collection, GetSerializerDelegate getSerializer)
        {
            // On save, the MigratorDataProvider purges items from the source and saves them in the target. 
            // Information messages for the Purge should not be returned to the caller because they are
            // only expecting save messages. However, non-Information messages (i.e. Error or Warning)
            // should be returned so the caller can understand just how sadly deranged their universe has become.

            // Get the serializer that will be used by the source
            ISerializerType xser = getSerializer (Source.SerializerKind);
            if (xser == null)
                throw new ArgumentException ("There is no serializer for the source collection");

            try
            {
                // Save the source serializer's logger - will call it if bad things happen
                LoggerDelegate saveLogger = xser.Logger;
                
                // Push a new logger onto the source serializer's stack of loggers
                xser.PushLogger(delegate(MajorEnum status, MinorEnum minorStatus, string message, object[] paras)
                {
                    if (status != MajorEnum.Information)
                        saveLogger(status, minorStatus, message, paras);
                    return status == MajorEnum.Error ? LoggerResponseEnum.giveUp : LoggerResponseEnum.keepGoing;
                });
                Source.PurgeItems(WrapperBase.ExternalIdsForItems(items), collection, getSerializer);
            }
            finally
            {
                // Pop the logger off the source serializer's logger stack. It would be 
                // very bad if we either did too many pops or not enough, hence the finally
                xser.PopLogger();
            }

            // It's possible that a call to GetItems might fail to see an item that has been 
            // purged from Source but has not yet been saved in Target (C'est la vie - tant pis)
            Target.SaveItems(items, collection, getSerializer);
        }

        public void Terminate(LoggerDelegate logger)
        {
            LoggerDelegate localLogger = delegate(MajorEnum status, MinorEnum minorStatus, string message, object[] paras)
            {
                if (status != MajorEnum.Information)
                    logger(status, minorStatus, message, paras);
                return status == MajorEnum.Error ? LoggerResponseEnum.giveUp : LoggerResponseEnum.keepGoing;
            };

            Target.Terminate(logger);
            Source.Terminate(localLogger);
        }

        #endregion

    }
}
